# -*- coding: utf-8 -*-
"""
    xfree.plugins.manager
    ~~~~~~~~~~~~~~~~~~~~~~~
    文件描述：XFree的插件管理器。
    作者：xfree
    邮箱：cy_83k@qq.com

"""

import logging
import pluggy
from pkg_resources import DistributionNotFound, VersionConflict, iter_entry_points
from xfree.utils.helpers import parse_pkg_metadata

# 创建一个日志记录器，用于记录插件管理器的相关信息。
logger = logging.getLogger(__name__)


class XFreePluginManager(pluggy.PluginManager):
    """重写`pluggy.PluginManager`类以添加 XFree 特定的内容。"""

    def __init__(self, project_name):
        """
        构造函数。
        :param project_name: 项目名称，用于初始化父类的 PluginManager。
        """
        # 调用父类的构造函数，初始化 PluginManager。
        super(XFreePluginManager, self).__init__(project_name=project_name)
        # 存储插件的元数据信息，键为插件名称，值为元数据字典。
        self._plugin_metadata = {}
        # 存储被禁用的插件，键为插件对象，值为包含插件名称和分布信息的元组。
        self._disabled_plugins = {}
        # 为XFree内部插件单独维护一个字典，键为插件名称，值为插件对象。
        self._internal_name2plugin = {}

    def register(self, plugin, name=None, internal=False):
        """注册一个插件。
        :param plugin: 要注册的插件对象。
        :param name: 插件的名称，如果未提供，则自动生成。
        :param internal: 布尔值，表示插件是否为内部插件。
        :return: 如果插件不是内部插件，返回其注册后的名称；否则返回 None。
        """
        # 使用父类的 register 方法注册插件，并获取插件名称。
        name = super(XFreePluginManager, self).register(plugin, name)
        if not internal:
            return name

        # 如果是内部插件，将其从普通插件字典中移除，并添加到内部插件字典中。
        self._internal_name2plugin[name] = self._name2plugin.pop(name)
        return name

    def unregister(self, plugin=None, name=None):
        """
        卸载一个插件。

        :param plugin: 要卸载的插件对象，如果未提供，则根据名称卸载。
        :param name: 要卸载的插件名称，如果未提供，则根据插件对象卸载。
        :return: 被卸载的插件对象。
        """
        # 使用父类的 unregister 方法卸载插件，并获取被卸载的插件对象。
        plugin = super(XFreePluginManager, self).unregister(plugin=plugin, name=name)

        # 获取插件的名称。
        name = self.get_name(plugin)
        if self._internal_name2plugin.get(name):
            # 如果是内部插件，从内部插件字典中删除该插件。
            del self._internal_name2plugin[name]

        return plugin

    def set_blocked(self, name):
        """
        阻止给定名称的插件注册，如果该插件已经注册，则卸载它。

        :param name: 要阻止注册的插件名称。
        """
        super(XFreePluginManager, self).set_blocked(name)
        # 如果是内部插件，将其在内部插件字典中的值设置为 None。
        self._internal_name2plugin[name] = None

    def is_blocked(self, name):
        """
        检查给定名称的插件是否被阻止注册。

        :param name: 要检查的插件名称。
        :return: 如果插件被阻止注册，返回 True；否则返回 False。
        """
        blocked = super(XFreePluginManager, self).is_blocked(name)

        return (
            blocked
            or name in self._internal_name2plugin
            and self._internal_name2plugin[name] is None
        )

    def get_plugin(self, name):
        """
        根据名称获取插件对象。

        :param name: 插件名称。
        :return: 如果是内部插件，返回内部插件字典中的插件对象；否则返回普通插件字典中的插件对象。
        """
        plugin = super(XFreePluginManager, self).get_plugin(name)
        return self._internal_name2plugin.get(name, plugin)

    def get_name(self, plugin):
        """
        根据插件对象获取其名称。

        :param plugin: 插件对象。
        :return: 如果插件在普通插件字典中有对应名称，返回该名称；否则遍历内部插件字典，找到对应插件对象后返回其名称。
        """
        name = super(XFreePluginManager, self).get_name(plugin)
        if name:
            return name

        for name, val in self._internal_name2plugin.items():
            if plugin == val:
                return name

    def load_setuptools_entrypoints(self, entrypoint_name):
        """
        从指定的 setuptools 入口点加载插件。

        :param entrypoint_name: setuptools 入口点名称。
        :return: 加载的插件数量。
        """
        # 记录正在加载的入口点信息。
        logger.info("正在从入口点 {} 加载插件。".format(entrypoint_name))
        # 记录加载的插件数量。
        for ep in iter_entry_points(entrypoint_name):
            if self.get_plugin(ep.name):
                continue

            try:
                # 加载入口点对应的插件对象。
                plugin = ep.load()
            except DistributionNotFound:
                # 如果无法找到插件的分布信息，记录警告并继续。
                logger.warning("无法加载插件 {}. 跳过。".format(ep.name))
                continue
            except VersionConflict as e:
                # 如果存在版本冲突，抛出插件验证错误。
                raise pluggy.PluginValidationError(
                    "插件 %r 无法加载: %s!" % (ep.name, e)
                )

            if self.is_blocked(ep.name):
                # 如果插件被阻止注册，将其添加到禁用插件字典中，并记录元数据。
                self._disabled_plugins[plugin] = (ep.name, ep.dist)
                self._plugin_metadata[ep.name] = parse_pkg_metadata(ep.dist.key)
                continue

            # 注册插件，并将其添加到插件分布信息列表和元数据字典中，同时记录加载信息。
            self.register(plugin, name=ep.name)
            self._plugin_distinfo.append((plugin, ep.dist))
            self._plugin_metadata[ep.name] = parse_pkg_metadata(ep.dist.key)
            logger.info("已加载插件: {}".format(ep.name))
        # 记录加载的插件数量。
        logger.info(
            "已从入口点 {} 加载了 {} 个插件。".format(
                len(self._plugin_distinfo), entrypoint_name
            )
        )
        return len(self._plugin_distinfo)

    def get_metadata(self, name):
        """
        获取给定名称插件的元数据。

        :param name: 插件名称。
        :return: 插件的元数据字典，如果插件不存在，返回 None。
        """
        return self._plugin_metadata.get(name)

    def list_name(self):
        """
        返回仅包含已启用插件的名称列表。

        :return: 已启用插件名称列表。
        """
        return list(self._name2plugin.keys())

    def list_internal_name_plugin(self):
        """
        返回内部插件的名称和插件对象的键值对列表。

        :return: 内部插件名称和插件对象的键值对列表。
        """
        return self._internal_name2plugin.items()

    def list_plugin_metadata(self):
        """
        返回所有插件的元数据。

        :return: 插件元数据字典。
        """
        return self._plugin_metadata

    def list_disabled_plugins(self):
        """
        返回禁用插件的名称和分布信息的元组对列表。

        :return: 禁用插件的名称和分布信息的元组对列表。
        """
        return self._disabled_plugins.values()

    def get_disabled_plugins(self):
        """
        返回禁用插件的列表。

        :return: 禁用插件的列表。
        """
        return self._disabled_plugins.keys()

    def get_internal_plugins(self):
        """
        返回已注册的内部插件集合。

        :return: 已注册的内部插件集合。
        """
        return set(self._internal_name2plugin.values())

    def get_external_plugins(self):
        """
        返回已注册的外部插件集合。

        :return: 已注册的外部插件集合。
        """
        return set(self.get_plugins() - self.get_internal_plugins())